// 创建字典的构造函数
function Dictionary() {
    // 字典属性
    this.items = {}

    // 字典操作方法
    // 1 添加键值对
    Dictionary.prototype.set = function (key, value) {  
        this.items[key] = value;
    }

    // 2 判断字典中是否有某个key
    Dictionary.prototype.has = function (key) {
        return this.items[key] != null
    }

    // 3 从字典中移除元素remove
    Dictionary.prototype.remove = function (key) {  
        delete this.items[key]
        return true
    }

    // 4 根据key去获取value 
    Dictionary.prototype.get = function (key) {  
        return this.has(key) ? this.items[key] : undefined
    }

    // 5 获取多有的keys
    Dictionary.prototype.keys = function () {  
        return Object.keys(this.items)
    }

    // 6 获取所有的value
    Dictionary.prototype.values = function () {  
        return Object.values(this.items)
    }

    // 7 size方法
    Dictionary.prototype.size = function () {  
        // return Object.keys(this.items).length
        return this.keys().length
    }

    // 8 clear方法
    Dictionary.prototype.clear = function () {  
        this.items = {}
    }
}

// 创建队列结构
function Queue () {
    // 封装属性
    this.items = [];

    // 1 实现插入
    Queue.prototype.enqueue = function (element){
        this.items.push(element)
    }

    // 2 从队列中删除前端元素
    Queue.prototype.dequeue = function (){
        return this.items.shift()
    }

    // 3 查看前端元素
    Queue.prototype.front = function (){
        return this.items[0]
    }

    // 4 查看队列是否为空
    Queue.prototype.isEmpty = function (){
        return this.items.length === 0
    }

    // 5 查看队列个数
    Queue.prototype.size = function (){
        return this.items.length
    }

    // 6 toString方法
    Queue.prototype.toString = function () {  
        let resultString = '';
        for (let index = 0; index < this.items.length; index++) {
            resultString += this.items[i] + ' ';            
        }
        return resultString
    }
}
//-----------------分割线--------------------------------

// 封装图结构
function Graph() {
    // 属性:顶点(数组)/ 边(字典)
    this.vertexes = []; //顶点
    this.edges = new Dictionary(); //边

    // 方法
    // 添加方法
    // 1 添加顶点的方法
    Graph.prototype.addVertex = function (vertex) {  
        this.vertexes.push(vertex);
        this.edges.set(vertex, []);
    }

    // 2 添加边的方法
    Graph.prototype.addEdge = function (vertexA, vertexB) {  
        this.edges.get(vertexA).push(vertexB);
        this.edges.get(vertexB).push(vertexA);
    }

    // 3 实现toString方法
    Graph.prototype.toString = function () {  
        // 定义字符串,保存最终结果
        let resultString = "";

        // 遍历所有的顶点,以及顶点对应的边
        for (let index = 0; index < this.vertexes.length; index++) {
            resultString += this.vertexes[index] + "->";
            let vEdges = this.edges.get(this.vertexes[index]);
            for (let edgeIndex = 0; edgeIndex < vEdges.length; edgeIndex++) {
                resultString += vEdges[edgeIndex] + " "
            }            
            resultString += "\n"
        }
        return resultString
    }

    // 4 实现图搜索(BFS & DFS)
    // 4.1 初始化顶点状态颜色
    Graph.prototype.initializeColor = function () {  
        let colors = [];
        for (let index = 0; index < this.vertexes.length; index++) {
            // 白色white 未遍历处理, 灰色gray 已遍历未处理, 黑色black 已被遍历 已处理
            colors[this.vertexes[index]] = "white" ; 
        }
        return colors
    }

    // 4.2 广度优先搜索(BFS) 队列处理
    // firstV表示传入的首个顶点, handle表传入的处理函数
    Graph.prototype.bfs = function (firstV, handler) {  
        // 1 初始化颜色
        let colors = this.initializeColor()

        // 2 创建队列
        let queue = new Queue();

        // 3 将顶点加入到队列中,更改颜色
        queue.enqueue(firstV);
        colors[firstV] = "gray";

        // 4 循环从队列中取出元素
        while (! queue.isEmpty()) {
            // 4.1 从队列中取出头元素,对其进行handle处理,并更改颜色
            let v = queue.dequeue();
            // 访问处理顶点
            handler(v);

            // 白色white 未遍历处理, 灰色gray 已遍历未处理, 黑色black 已被遍历 已处理
            colors[v] = "black"; 

            // 4.2 获取与顶点相连的其它顶点
            let vList = this.edges.get(v);

            // 4.3 将获取到的其它顶点加入到队列中
            for (let index = 0; index < vList.length; index++) {
                let otherV = vList[index];
                // 判断顶点是否已经遍历过(是否未白色)
                if (colors[otherV] == "white") {
                    colors[otherV] = "gray";  // 更改颜色
                    queue.enqueue(otherV);            
                }
            }

            // 将节点变为黑色
            colors[firstV] = "black";
        }
    }

    // 4.3 深度优先搜索(DFS) 栈或者递归
    Graph.prototype.dfs = function (firstV, handler) {  
        // 1 初始化颜色
        let colors = this.initializeColor();

        // 2 从某个顶点开始依次递归访问
        this.dfsVisit(firstV, colors, handler);
    }

    Graph.prototype.dfsVisit = function (v, colors, handler) {  
        // 1 更改颜色
        colors[v] = "gray";

        // 2 处理顶点
        handler(v);

        // 3 递归遍历其它节点
        // 3.1 获取该顶点所连接项
        let vList = this.edges.get(v);
        for (let index = 0; index < vList.length; index++) {
            let vListItem = vList[index];
            if (colors[vListItem] == "white") {
                this.dfsVisit(vListItem, colors, handler)
            }      
        }

        // 将该节点变为黑色
        colors[v] = "black";
    }

}

// 测试代码
// 1 创建图结构
let graph = new Graph();

// 2 添加顶点
let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
for (let index = 0; index < myVertexes.length; index++) {
    graph.addVertex(myVertexes[index]);    
}

// 3 添加边
let myEdges = [
    ['A', 'B'],
    ['A', 'C'],
    ['A', 'D'],
    ['C', 'D'],
    ['C', 'G'],
    ['D', 'G'],
    ['D', 'H'],
    ['B', 'E'],
    ['B', 'F'],
    ['E', 'I'],
]

for (let index = 0; index < myEdges.length; index++) {
    let value = myEdges[index];
    graph.addEdge(value[0], value[1]);    
} 

console.log(graph.toString());

// 4 测试BFS搜索
console.log("BFS搜索开始:");
let result = '';
graph.bfs(graph.vertexes[0], function (vertex) {  
    result += vertex + ' ';
})
console.log(result);

// 5 测试DFS搜索
console.log("DFS搜索开始:");
result = '';
graph.dfs(graph.vertexes[0], function (vertex) {  
    result += vertex + ' ';
})
console.log(result);